item->_modtime = FileSystem::getModTime(newFilePathAbsolute);
if (item->_modtime <= 0) {
- _pendingChecksumFiles.remove(item->_file);
- slotOnErrorStartFolderUnlock(item, SyncFileItem::NormalError, tr("File %1 has invalid modified time. Do not upload to the server.").arg(QDir::toNativeSeparators(item->_file)), ErrorCategory::GenericError);
- checkPropagationIsDone();
- return;
+ const auto now = QDateTime::currentSecsSinceEpoch();
+ qCInfo(lcPropagateUpload) << "File" << item->_file << "has invalid modification time of" << item->_modtime << "-- trying to update it to" << now;
+ if (FileSystem::setModTime(newFilePathAbsolute, now)) {
+ item->_modtime = now;
+ } else {
+ qCWarning(lcPropagateUpload) << "Could not update modification time for" << item->_file;
+ _pendingChecksumFiles.remove(item->_file);
+ slotOnErrorStartFolderUnlock(item, SyncFileItem::NormalError, tr("File %1 has invalid modified time. Do not upload to the server.").arg(QDir::toNativeSeparators(item->_file)), ErrorCategory::GenericError);
+ checkPropagationIsDone();
+ return;
+ }
}
}
return;
}
- const auto prevModtime = item->_modtime; // the _item value was set in PropagateUploadFile::start()
+ const auto prevModtime = item->_modtime; // the item value was set in PropagateUploadFile::start()
// but a potential checksum calculation could have taken some time during which the file could
// have been changed again, so better check again here.
item->_modtime = FileSystem::getModTime(originalFilePath);
+ qCDebug(lcPropagateUpload) << "fullFilePath" << fullFilePath << "originalFilePath" << originalFilePath << "prevModtime" << prevModtime << "item->_modtime" << item->_modtime;
if (item->_modtime <= 0) {
- _pendingChecksumFiles.remove(item->_file);
- slotOnErrorStartFolderUnlock(item, SyncFileItem::NormalError, tr("File %1 has invalid modification time. Do not upload to the server.").arg(QDir::toNativeSeparators(item->_file)), ErrorCategory::GenericError);
- checkPropagationIsDone();
- return;
+ const auto now = QDateTime::currentSecsSinceEpoch();
+ qCInfo(lcPropagateUpload) << "File" << item->_file << "has invalid modification time of" << item->_modtime << "-- trying to update it to" << now;
+ if (FileSystem::setModTime(originalFilePath, now)) {
+ item->_modtime = now;
+ } else {
+ qCWarning(lcPropagateUpload) << "Could not update modification time for" << item->_file;
+ _pendingChecksumFiles.remove(item->_file);
+ slotOnErrorStartFolderUnlock(item, SyncFileItem::NormalError, tr("File %1 has invalid modification time. Do not upload to the server.").arg(QDir::toNativeSeparators(item->_file)), ErrorCategory::GenericError);
+ checkPropagationIsDone();
+ return;
+ }
}
- if (prevModtime != item->_modtime) {
+ if (prevModtime > 0 && prevModtime != item->_modtime) {
propagator()->_anotherSyncNeeded = true;
_pendingChecksumFiles.remove(item->_file);
}
if ((item->_direction == SyncFileItem::Down || item->_instruction == CSYNC_INSTRUCTION_CONFLICT || item->_instruction == CSYNC_INSTRUCTION_NEW || item->_instruction == CSYNC_INSTRUCTION_SYNC) &&
+ item->_direction != SyncFileItem::Up &&
(item->_modtime <= 0 || item->_modtime >= 0xFFFFFFFF)) {
item->_instruction = CSYNC_INSTRUCTION_ERROR;
item->_errorString = tr("Cannot sync due to invalid modification time");
// probably temporary one.
_item->_modtime = FileSystem::getModTime(filePath);
if (_item->_modtime <= 0) {
- slotOnErrorStartFolderUnlock(SyncFileItem::NormalError, tr("File %1 has invalid modification time. Do not upload to the server.").arg(QDir::toNativeSeparators(_item->_file)));
- return;
+ const auto now = QDateTime::currentSecsSinceEpoch();
+ qCInfo(lcPropagateUpload) << "File" << _item->_file << "has invalid modification time of" << _item->_modtime << "-- trying to update it to" << now;
+ if (FileSystem::setModTime(filePath, now)) {
+ _item->_modtime = now;
+ } else {
+ qCWarning(lcPropagateUpload) << "Could not update modification time for" << _item->_file;
+ slotOnErrorStartFolderUnlock(SyncFileItem::NormalError, tr("File %1 has invalid modification time. Do not upload to the server.").arg(QDir::toNativeSeparators(_item->_file)));
+ return;
+ }
}
const QByteArray checksumType = propagator()->account()->capabilities().preferredUploadChecksumType();
QVector<FileInfo *> FakePutMultiFileReply::performMultiPart(FileInfo &remoteRootFileInfo, const QNetworkRequest &request, const QByteArray &putPayload, const QString &contentType)
{
+ Q_UNUSED(request)
QVector<FileInfo *> result;
auto stringPutPayload = QString::fromUtf8(putPayload);
// Assume that the file is filled with the same character
fileInfo = remoteRootFileInfo.create(fileName, onePartBody.size(), onePartBody.at(0).toLatin1());
}
- fileInfo->lastModified = OCC::Utility::qDateTimeFromTime_t(request.rawHeader("x-oc-mtime").toLongLong());
+ fileInfo->lastModified = OCC::Utility::qDateTimeFromTime_t(modtime);
remoteRootFileInfo.find(fileName, /*invalidateEtags=*/true);
result.push_back(fileInfo);
}
QCOMPARE(fakeFolder.currentRemoteState(), expectedState);
}
+ void testLocalInvalidMtimeCorrection()
+ {
+ const auto INVALID_MTIME = QDateTime::fromSecsSinceEpoch(0);
+ const auto RECENT_MTIME = QDateTime::fromSecsSinceEpoch(1743004783); // 2025-03-26T16:59:43+0100
+
+ FakeFolder fakeFolder{FileInfo{}};
+ QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+
+ fakeFolder.localModifier().insert(QStringLiteral("invalid"));
+ fakeFolder.localModifier().setModTime("invalid", INVALID_MTIME);
+ fakeFolder.localModifier().insert(QStringLiteral("recent"));
+ fakeFolder.localModifier().setModTime("recent", RECENT_MTIME);
+
+ QVERIFY(fakeFolder.syncOnce());
+
+ // "invalid" file had a mtime of 0, so it's been updated to the current time during testing
+ const auto currentMtime = fakeFolder.currentLocalState().find("invalid")->lastModified;
+ QCOMPARE_GT(currentMtime, RECENT_MTIME);
+ QCOMPARE_GT(fakeFolder.currentRemoteState().find("invalid")->lastModified, RECENT_MTIME);
+
+ // "recent" file had a mtime of RECENT_MTIME, so it shouldn't have been changed
+ QCOMPARE(fakeFolder.currentLocalState().find("recent")->lastModified, RECENT_MTIME);
+ QCOMPARE(fakeFolder.currentRemoteState().find("recent")->lastModified, RECENT_MTIME);
+
+ QVERIFY(fakeFolder.syncOnce());
+
+ // verify that the mtime of "invalid" hasn't changed since the last sync that fixed it
+ QCOMPARE(fakeFolder.currentLocalState().find("invalid")->lastModified, currentMtime);
+
+ QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+ }
+
+ void testLocalInvalidMtimeCorrectionBulkUpload()
+ {
+ const auto INVALID_MTIME = QDateTime::fromSecsSinceEpoch(0);
+ const auto RECENT_MTIME = QDateTime::fromSecsSinceEpoch(1743004783); // 2025-03-26T16:59:43+0100
+
+ FakeFolder fakeFolder{FileInfo{}};
+ QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+ fakeFolder.syncEngine().account()->setCapabilities({ { "dav", QVariantMap{ {"bulkupload", "1.0"} } } });
+
+ fakeFolder.localModifier().insert(QStringLiteral("invalid"));
+ fakeFolder.localModifier().setModTime("invalid", INVALID_MTIME);
+ fakeFolder.localModifier().insert(QStringLiteral("recent"));
+ fakeFolder.localModifier().setModTime("recent", RECENT_MTIME);
+
+ QVERIFY(fakeFolder.syncOnce()); // this will use the BulkPropagatorJob
+
+ // "invalid" file had a mtime of 0, so it's been updated to the current time during testing
+ const auto currentMtime = fakeFolder.currentLocalState().find("invalid")->lastModified;
+ QCOMPARE_GT(currentMtime, RECENT_MTIME);
+ QCOMPARE_GT(fakeFolder.currentRemoteState().find("invalid")->lastModified, RECENT_MTIME);
+
+ // "recent" file had a mtime of RECENT_MTIME, so it shouldn't have been changed
+ QCOMPARE(fakeFolder.currentLocalState().find("recent")->lastModified, RECENT_MTIME);
+ QCOMPARE(fakeFolder.currentRemoteState().find("recent")->lastModified, RECENT_MTIME);
+
+ QVERIFY(fakeFolder.syncOnce()); // this will not propagate anything
+
+ // verify that the mtime of "invalid" hasn't changed since the last sync that fixed it
+ QCOMPARE(fakeFolder.currentLocalState().find("invalid")->lastModified, currentMtime);
+
+ QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
+ }
+
void testServerUpdatingMTimeShouldNotCreateConflicts()
{
constexpr auto testFile = "test.txt";
return {};
};
+ const auto lastModified = [&](const QString &path) -> qint64 {
+ return fakeFolder.currentLocalState().find(path)->lastModified.toSecsSinceEpoch();
+ };
+
fakeFolder.localModifier().insert(fooFileRootFolder);
fakeFolder.localModifier().insert(barFileRootFolder);
fakeFolder.localModifier().mkdir(QStringLiteral("subfolder"));
fakeFolder.scheduleSync();
fakeFolder.execUntilBeforePropagation();
- QCOMPARE(checkStatus(), SyncFileStatus::StatusError);
+ QCOMPARE(checkStatus(), SyncFileStatus::StatusSync);
fakeFolder.execUntilFinished();
+ // ensure mtime has changed after the sync
+ QCOMPARE_GT(lastModified(barFileAaaSubFolder), CURRENT_MTIME);
+
fakeFolder.localModifier().setModTime(barFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME));
QVERIFY(fakeFolder.syncOnce());
+ // ensure mtime is now CURRENT_MTIME
+ QCOMPARE(lastModified(barFileAaaSubFolder), CURRENT_MTIME);
+
fakeFolder.localModifier().appendByte(barFileAaaSubFolder);
fakeFolder.localModifier().setModTime(barFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(INVALID_MTIME1));
fakeFolder.scheduleSync();
fakeFolder.execUntilBeforePropagation();
- QCOMPARE(checkStatus(), SyncFileStatus::StatusError);
+ QCOMPARE(checkStatus(), SyncFileStatus::StatusSync);
fakeFolder.execUntilFinished();
+ // ensure mtime has changed after the sync
+ QCOMPARE_GT(lastModified(barFileAaaSubFolder), CURRENT_MTIME);
+
fakeFolder.localModifier().setModTime(barFileAaaSubFolder, QDateTime::fromSecsSinceEpoch(CURRENT_MTIME));
QVERIFY(fakeFolder.syncOnce());
fakeFolder.scheduleSync();
fakeFolder.execUntilBeforePropagation();
- QCOMPARE(checkStatus(), SyncFileStatus::StatusError);
+ QCOMPARE(checkStatus(), SyncFileStatus::StatusSync);
+
+ // the server only considers an mtime of 0-86400 (1d) as invalid, so this is fine
+ // see also: apps/dav/lib/Connector/Sabre/MtimeSanitizer.php
+ QCOMPARE(lastModified(barFileAaaSubFolder), INVALID_MTIME2);
fakeFolder.execUntilFinished();
}